﻿using System;

using Mono.Cecil;
using Mono.Cecil.Cil;

namespace il2lua.TypeSystem;
public static class MethodMixins
{

    public static void SimplifyMacros(this IMethod self, IList<Instruction> instructions)
    {

        if (self == null)
        {
            throw new ArgumentNullException("self");
        }
        Console.WriteLine(self);
        var ilDict = new Dictionary<int, ILInstruction>();
        IInstruction Get(int offset, int index, Code code = Code.Nop, object? operand = null)
        {
            if (!ilDict.TryGetValue(offset, out var inst))
            {
                inst = new ILInstruction(index, code, operand);
                ilDict[offset] = inst;
            }
            else
            {
                Console.WriteLine("已存在！", offset);
                if (index != -1)
                    inst.Index = index;
                inst.OpCode = code;
                inst.Operand = operand;
            }
            return inst;
        }

        for (int i = 0; i < instructions.Count; i++)
        {
            Instruction instruction = instructions[i];
            Code code = instruction.OpCode.Code;
            var operand = instruction.Operand;
            var offset = instruction.Offset;
            if (operand is Instruction il)
                operand = Get(il.Offset, -1, il.OpCode.Code, il.Operand);
            switch (code)
            {
                case Code.Ldarg_0:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetParam(0 - 1)));
                    break;
                case Code.Ldarg_1:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetParam(1 - 1)));
                    break;
                case Code.Ldarg_2:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetParam(2 - 1)));
                    break;
                case Code.Ldarg_3:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetParam(3 - 1)));
                    break;
                case Code.Ldloc_0:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetVariable(0)));
                    break;
                case Code.Ldloc_1:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetVariable(1)));
                    break;
                case Code.Ldloc_2:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetVariable(2)));
                    break;
                case Code.Ldloc_3:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetVariable(3)));
                    break;
                case Code.Stloc_0:
                    self.Instructions.Add(Get(offset, i, Code.Stloc, self.GetVariable(0)));
                    break;
                case Code.Stloc_1:
                    self.Instructions.Add(Get(offset, i, Code.Stloc, self.GetVariable(1)));
                    break;
                case Code.Stloc_2:
                    self.Instructions.Add(Get(offset, i, Code.Stloc, self.GetVariable(2)));
                    break;
                case Code.Stloc_3:
                    self.Instructions.Add(Get(offset, i, Code.Stloc, self.GetVariable(3)));
                    break;
                case Code.Ldarg_S:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetParam(((ParameterDefinition)operand).Index)));
                    break;
                case Code.Ldarga_S:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetParam(((ParameterDefinition)operand).Index)));
                    break;
                case Code.Starg_S:
                    self.Instructions.Add(Get(offset, i, Code.Stloc, self.GetParam(((ParameterDefinition)operand).Index)));
                    break;
                case Code.Ldloc_S:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetVariable(((VariableDefinition)operand).Index)));
                    break;
                case Code.Ldloca_S:
                    self.Instructions.Add(Get(offset, i, Code.Ldloc, self.GetVariable(((VariableDefinition)operand).Index)));
                    break;
                case Code.Stloc_S:
                    self.Instructions.Add(Get(offset, i, Code.Stloc, self.GetVariable(((VariableDefinition)operand).Index)));
                    break;
                case Code.Ldc_I4:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, (long)((int)operand)));
                    break;
                case Code.Ldc_R4:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_R8, (double)((float)operand)));
                    break;
                case Code.Ldc_I4_M1:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, -1l));
                    break;
                case Code.Ldc_I4_0:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 0l));
                    break;
                case Code.Ldc_I4_1:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 1l));
                    break;
                case Code.Ldc_I4_2:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 2l));
                    break;
                case Code.Ldc_I4_3:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 3l));
                    break;
                case Code.Ldc_I4_4:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 4l));
                    break;
                case Code.Ldc_I4_5:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 5l));
                    break;
                case Code.Ldc_I4_6:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 6l));
                    break;
                case Code.Ldc_I4_7:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 7l));
                    break;
                case Code.Ldc_I4_8:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, 8l));
                    break;
                case Code.Ldc_I4_S:
                    self.Instructions.Add(Get(offset, i, Code.Ldc_I8, (long)((sbyte)operand)));
                    break;
                case Code.Br_S:
                    self.Instructions.Add(Get(offset, i, Code.Br, operand));
                    break;
                case Code.Brfalse_S:
                    self.Instructions.Add(Get(offset, i, Code.Brfalse, operand));
                    break;
                case Code.Brtrue_S:
                    self.Instructions.Add(Get(offset, i, Code.Brtrue, operand));
                    break;
                case Code.Beq_S:
                    self.Instructions.Add(Get(offset, i, Code.Beq, operand));
                    break;
                case Code.Bge_S:
                    self.Instructions.Add(Get(offset, i, Code.Bge, operand));
                    break;
                case Code.Bgt_S:
                    self.Instructions.Add(Get(offset, i, Code.Bgt, operand));
                    break;
                case Code.Ble_S:
                    self.Instructions.Add(Get(offset, i, Code.Ble, operand));
                    break;
                case Code.Blt_S:
                    self.Instructions.Add(Get(offset, i, Code.Blt, operand));
                    break;
                case Code.Bne_Un_S:
                    self.Instructions.Add(Get(offset, i, Code.Bne_Un, operand));
                    break;
                case Code.Bge_Un_S:
                    self.Instructions.Add(Get(offset, i, Code.Bge_Un, operand));
                    break;
                case Code.Bgt_Un_S:
                    self.Instructions.Add(Get(offset, i, Code.Bgt_Un, operand));
                    break;
                case Code.Ble_Un_S:
                    self.Instructions.Add(Get(offset, i, Code.Ble_Un, operand));
                    break;
                case Code.Blt_Un_S:
                    self.Instructions.Add(Get(offset, i, Code.Blt_Un, operand));
                    break;
                default:
                    if (code == Code.Leave_S)
                        self.Instructions.Add(Get(offset, i, Code.Leave, operand));
                    else
                        self.Instructions.Add(Get(offset, i, code, operand));
                    break;
            }
        }
        foreach (var inst in self.Instructions)
        {
            Console.WriteLine("    " + inst);
        }
        Console.WriteLine();
    }
}
